#!/usr/bin/python

#    dgr_wff.py - Configurable We Feel Fine / Twitter DGR.
#    Copyright 2009 Rob Myers <rob@robmyers.org>
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

################################################################################
# Imports
################################################################################

import feedparser
import re
import sys
import urllib

import twitter
import yaml

################################################################################
# Configuration
################################################################################

dgr_username = None
dgr_password = None
dgr_count = None
dgr_search = None

def check_parameters ():
    '''Make sure the script is being called correctly.'''
    if (len (sys.argv) != 2):
        print ("Please call with the path of a yaml file representing a dgr.")
        exit (1)

def check_yaml (yaml):
    '''Make sure all required fields are in the yaml.'''
    if (not (yaml["username"] and yaml["password"] and yaml["count"] and \
             yaml["search"])):
        print ("Make sure yaml contains username, password, count & search.")
        exit (1)

def load_yaml ():
    '''Load the configuration from the yaml filename on the command line.'''
    global dgr_username, dgr_password, dgr_count, dgr_search
    yaml_stream = file (sys.argv[1], 'r')
    dgr = yaml.safe_load (yaml_stream)
    check_yaml (dgr)
    dgr_username = dgr["username"]
    dgr_password = dgr["password"]
    dgr_count = dgr["count"]
    dgr_search = dgr["search"]

################################################################################
# Tweets
################################################################################

class tweet:
    def __init__(self, _who, _what, _where):
	self.who = _who
    	self.what = _what
	self.where = _where

def search_twitter (term):
    '''Get the search rss feed from twitter for the given single term.'''
    return feedparser.parse('http://search.twitter.com/search.atom?q=' + term)\
    	   ['entries']

def usable_tweet (user, content):
    '''Make sure the tweet isn't already a retweet'''
    # We don't need to check for our own tweets, they are all retweets anyway
    return (content[0:2].lower().lstrip() != 'rt')

def strip_html_tags (html):
    '''Strip html tags, hopefully leave smileys unharmed.'''
    return re.sub(r'<[^>]+>', '', html) 

def parse_twitter_search_results (search_results):
    '''Parse the list of *usable* feed items into tweet objects'''
    tweets = []
    for result in search_results:
	# FIXME for efficiency break on first space only, not every space
	tweet_user = result.author.split(' ')[0]
	text = strip_html_tags (result.content[0].value)
    	if usable_tweet (tweet_user, text):
	    url = result.link
    	    tweets.append (tweet (tweet_user, text, url))
    return tweets
	    
def shorten_url (url):
    '''Call ur1.ca for a short URL'''
    params = urllib.urlencode({'longurl': url})
    page = urllib.urlopen ("http://ur1.ca/", params).read ()
    # FIXME Fragile. Assumes first url on returned page is the one we want
    return re.search ('http://ur1.ca/[^"]+', page, re.M).group (0)

def tweet_to_retweet (tweet):
    '''Make a standard retweet with the content guaranteed to contain the url'''
    separator = ' '
    retweet = 'RT @' + tweet.who + separator + tweet.what
    if (len (retweet) > 140):
        short_url = shorten_url (tweet.where)
        truncate = 140 - (len (short_url) + len (separator))
        retweet = retweet[0:truncate] + separator + short_url
    return retweet

################################################################################
# We Feel Fine API
################################################################################

def search_wff (search):
    '''Search wff using the provided, complete, url.'''
    f = urllib.urlopen(search)
    return f.read()

def wff_emotions (search_results):
    '''Parse the list of *usable* feed items to get emotion keywords'''
    return re.findall (r"feeling=\"([^\"]+)\"", search_results)

################################################################################
# Searching on twitter, and retweeting
################################################################################

def retweet_feeling_matches (api, feelings):
    '''Search for feelings and retweet the results.'''
    for feeling in feelings:
        search_results = search_twitter (feeling)
        usable_tweets = parse_twitter_search_results (search_results)
        if (len (usable_tweets) > 0):
            # Get the first usable tweet
            first_tweet = usable_tweets[0]
            # Format it up into a retweet
            retweet = tweet_to_retweet (first_tweet)
            # And call the twitter API
            #print (retweet) # For testing
            post_tweet (api, retweet)

################################################################################
# Main flow of execution
################################################################################

def lifecycle ():
    '''The main function to call.'''
    # Make sure we're ok to run
    check_parameters ()
    # Load the configuration file
    load_yaml ()
    # Run the DGR's search against wff
    wff_xml = search_wff (dgr_search)
    feelings = wff_emotions (wff_xml)
    # Connect to twitter
    api = twitter.Api(username = dgr_username, password = dgr_password)
    # Search for the wff feelings on Twitter, and re-tweet the results
    retweet_feeling_matches (api, feelings[:dgr_count])


lifecycle ()
